﻿using Fancyx.Core.Interfaces;
using Fancyx.DataAccess.BaseEntity;
using Fancyx.DataAccess.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System.Linq.Expressions;

namespace Fancyx.DataAccess
{
    internal class Repository<T> : IRepository<T> where T : class
    {
        private readonly FancyxDbContext _context;
        private readonly ICurrentUser _currentUser;
        private static readonly Type _fullAuditedEntityType = typeof(FullAuditedEntity);
        private static readonly Type _efCoreExtensionType = typeof(EfCoreExtension);
        private static readonly Type _currentType = typeof(T);

        public Repository(FancyxDbContext context, ICurrentUser currentUser)
        {
            _context = context;
            _currentUser = currentUser;
        }

        public Task<bool> AnyAsync(Expression<Func<T, bool>> whereExpression)
        {
            return _context.Set<T>().AsNoTracking().AnyAsync(whereExpression);
        }

        public Task<int> CountAsync(Expression<Func<T, bool>> whereExpression)
        {
            return _context.Set<T>().AsNoTracking().CountAsync(whereExpression);
        }

        public async Task<int> DeleteAsync(Expression<Func<T, bool>> whereExpression)
        {
            var query = _context.Set<T>().AsNoTracking().Where(whereExpression);
            if (_fullAuditedEntityType.IsAssignableFrom(_currentType))
            {
                var softDeleteMethod = _efCoreExtensionType.GetMethod(nameof(EfCoreExtension.SoftDeleteAsync), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                if(softDeleteMethod != null)
                {
                    softDeleteMethod = softDeleteMethod.MakeGenericMethod(_currentType);
                    var task = (Task?)softDeleteMethod.Invoke(null, [query, _currentUser.Id]);
                    if (task != null)
                    {
                        await task;
                        return -1;
                    }
                }
            }
            return await query.ExecuteDeleteAsync();
        }

        public Task<List<T>> GetListAsync(Expression<Func<T, bool>> whereExpression)
        {
            return _context.Set<T>().AsNoTracking().Where(whereExpression).ToListAsync();
        }

        public async Task<EntityPaged<T>> GetPagedListAsync(int current, int pageSize, Expression<Func<T, bool>> whereExpression)
        {
            var query = _context.Set<T>().AsNoTracking().Where(whereExpression);
            return await query.PagedAsync(current, pageSize);
        }

        public IQueryable<T> GetQueryable()
        {
            return _context.Set<T>();
        }

        public async Task<int> InsertAsync(T entity, bool autoSave = true)
        {
            _context.Set<T>().Add(entity);
            return autoSave ? await _context.SaveChangesAsync() : -1;
        }

        public async Task<int> InsertManyAsync(List<T> entities, bool autoSave = true)
        {
            if (entities.Count > 500)
            {
                var now = DateTime.Now;
                foreach (var entity in entities)
                {
                    if (entity is CreationEntity value)
                    {
                        value.CreatorId ??= _currentUser.Id;
                        if (value.CreationTime == default) value.CreationTime = now;
                    }
                }
                await _context.Set<T>().BulkInsertAsync(entities);
                return entities.Count;
            }
            _context.Set<T>().AddRange(entities);
            return autoSave ? await _context.SaveChangesAsync() : -1;
        }

        public Task<T?> GetAsync(Expression<Func<T, bool>> whereExpression)
        {
            return _context.Set<T>().FirstOrDefaultAsync(whereExpression);
        }

        public ValueTask<T?> FindAsync<TKey>(TKey id)
        {
            return _context.Set<T>().FindAsync(id);
        }

        public async Task<int> UpdateAsync(T entity, bool autoSave = true)
        {
            if (_context.Entry(entity).State != EntityState.Modified)
            {
                _context.Set<T>().Update(entity);
            }
            return autoSave ? await _context.SaveChangesAsync() : -1;
        }

        public async Task<int> UpdateManyAsync(List<T> entities, bool autoSave = true)
        {
            if (entities.Count > 500)
            {
                var now = DateTime.Now;
                foreach (var entity in entities)
                {
                    if (entity is AuditedEntity value)
                    {
                        value.LastModifierId ??= _currentUser.Id;
                        if (value.LastModificationTime == default) value.LastModificationTime = now;
                    }
                }
                await _context.Set<T>().BulkUpdateAsync(entities);
                return entities.Count;
            }
            _context.Set<T>().UpdateRange(entities);
            return autoSave ? await _context.SaveChangesAsync() : -1;
        }

        public IQueryable<T> Where(Expression<Func<T, bool>> whereExpression)
        {
            return _context.Set<T>().Where(whereExpression);
        }

        public Task<List<T>> GetListAsync()
        {
            return _context.Set<T>().AsNoTracking().ToListAsync();
        }

        public async Task<int> DeleteAsync(T entity, bool autoSave = true)
        {
            if (entity is FullAuditedEntity val)
            {
                val.Delete(_currentUser.Id.GetValueOrDefault());
                var entry2 = _context.Entry(val);
                if (entry2.State == EntityState.Detached)
                {
                    _context.Attach(val);
                    entry2.Property(e => e.IsDeleted).IsModified = true;
                    entry2.Property(e => e.DeleterId).IsModified = true;
                    entry2.Property(e => e.DeletionTime).IsModified = true;
                }
                else
                {
                    SoftDeleteBeforeResetOtherProperty(entry2);
                }
            }
            else
            {
                _context.Set<T>().Remove(entity);
            }
            return autoSave ? await _context.SaveChangesAsync() : -1;
        }

        private static void SoftDeleteBeforeResetOtherProperty(EntityEntry entry)
        {
            foreach (var property in entry.Properties)
            {
                if (property.Metadata.Name != nameof(FullAuditedEntity.IsDeleted) &&
                    property.Metadata.Name != nameof(FullAuditedEntity.DeleterId) &&
                    property.Metadata.Name != nameof(FullAuditedEntity.DeletionTime))
                {
                    property.IsModified = false;
                }
            }
        }
    }
}